
package rcpsp;

import GUI.GanttDiagram;
import data.Data;
import data.Job;
import ilog.concert.IloException;
import ilog.concert.IloIntVar;
import ilog.concert.IloLinearIntExpr;
import ilog.concert.IloNumVar;
import ilog.cplex.IloCplex;
import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jfree.ui.RefineryUtilities;

// Model with forbidden configurations generated dynamically
public class ModelForbidConfig2 extends Model 
{
    
    public ModelForbidConfig2(IloCplex cplex, Data data){
        super(cplex, data);
    }
    
    private IloIntVar[] S;
    private IloIntVar[][] x;
    private ArrayList<ArrayList<Job>> allForbidConfigs;    
    
    @Override
    public void createModel() throws IloException 
    {        
        //Big M
        Integer M=data.getHorizon();
        
        S = cplex.intVarArray(data.getNbJobs()+2, 0, Integer.MAX_VALUE);
        x = new IloIntVar[data.getNbJobs()+2][data.getNbJobs()+2];
        
        for (int i = 0; i < data.getNbJobs()+2; i++) 
        {
            x[i] = cplex.boolVarArray(data.getNbJobs()+2);
        }
        
        cplex.addMinimize(S[data.getNbJobs()+1]);
        
        for (int i = 0; i < data.getNbJobs()+2; i++) 
        {
            cplex.addEq(x[i][i], 0);
        }
        
        for (int i=0; i<data.getNbJobs()+2;i++) 
        {
            for (Job j : data.getJobs().get(i).getSuccessors()) 
            {
                cplex.addEq(x[i][data.getJobs().indexOf(j)], 1);
            }
        }
        
        for (int i = 0; i < data.getNbJobs()+2; i++) 
        {
            for (int j = i+1; j < data.getNbJobs()+2; j++) 
            {
                cplex.addLe(cplex.sum(x[i][j], x[j][i]),1);
            }
        }
        
        for (int i = 0; i < data.getNbJobs()+2; i++) 
        {
            for (int j = 0; j < data.getNbJobs()+2; j++) 
            {
                for (int k = 0; k < data.getNbJobs()+2; k++)
                {
                    IloLinearIntExpr expr = cplex.linearIntExpr();
                    expr.addTerm(1, x[i][k]);
                    expr.addTerm(-1, x[i][j]);
                    expr.addTerm(-1, x[j][k]);
                    cplex.addGe(expr, -1);
                }
            }
        }        
         
        for (int i = 0; i < data.getNbJobs()+2; i++) 
        {
            for (int j = 0; j < data.getNbJobs()+2; j++) 
            {
                IloLinearIntExpr expr = cplex.linearIntExpr();
                expr.addTerm(1, S[j]);
                expr.addTerm(-1, S[i]);
                expr.addTerm(-data.getJobs().get(i).getProcessTime(), x[i][j]);
                expr.addTerm(-M, x[i][j]);
                cplex.addGe(expr, -M);
            }
        }
        
        
    }    
    
    @Override
    public long solve() throws IloException
    {   
        ArrayList<Integer> startsJobs = new ArrayList<>();
        long timeToSolve = System.nanoTime();
        
        //Disable cplex informations log output
        //cplex.setOut(null);
        
        this.allForbidConfigs = new ArrayList<>();        
        boolean toBeAdded = true; // 1 if a new configuration 
        // will be added, i.e. the ressource constraint 
        // is violated by a subset of jobs
         
        while(toBeAdded)
        {
            if(!cplex.solve()){
                throw new  IloException("No optimal solution found");
            }
            
            ArrayList<ArrayList<Job>> ForbidConfigs = new ArrayList<>();
            
            //valable qu'avec des releaseDate nulles;
            ArrayList<ArrayList<Job>> decoupeHorizon = new ArrayList<>();
            
            //Date of end of project in the current optimal solution
            int dateFinPlus1 = (int) Math.round(cplex.getValue(S[data.getNbJobs()+1])) + 1;
            
            for (int i = 0; i < dateFinPlus1; i++) {
                decoupeHorizon.add(new ArrayList<Job>());
            }
            
            //discretizes time to know all jobs that are executed together at the same time
            for (int i = 0; i < data.getNbJobs() + 2; i++) {
                for (int j = 0; j < data.getJobs().get(i).getProcessTime(); j++) {
                    decoupeHorizon.get( (int) Math.round(cplex.getValue(S[i]))+j).add(data.getJobs().get(i));
                }
            }
            
            //Ici il faut choisir : mettre toutes les configs interdites rencontrées,
            //ou bien n'ajouter que la première rencontrée ?
            // idée sous-jacente : si on n'ajoute plus d'une configuration à la fois
            //  (par exemple toutes) peut on se retrouver avec une solution non optimale
            //  pour le problème de départ ? (ie temps trop long par dépassement)
            //  REPONSE : normalement non parce que des jobs qui ne peuvent pas être
            //  placés en même temps, ne peuvent pas l'être dans aucune solution....
            
            //Un dernier truc : on peut soit :
            //      - Regarder aveuglément chaque instant de decoupeHorizon et
            //                          ajouter si c'est une config interdite
            //      - Essayer de ne pas mettre deux fois la même configuration interdite
            //                      (mais peut vite rajouter de la complexité, et 
            //                       si on veut limiter ça, on n'ajoutera pas toutes
            //                       les configurations interdites.
            toBeAdded = false;
            
            for (int r = 0; r < data.getNbRess(); r++) {
                
                for (int i = 0; i < dateFinPlus1; i++) {
                    int sommeConsomRess = 0;
                    ArrayList<Job> tempJob = new ArrayList<>();
                    //int minTime = Integer.MAX_VALUE;
                    for (int j = 0; j < decoupeHorizon.get(i).size(); j++) {
                        sommeConsomRess += decoupeHorizon.get(i).get(j).getAmountRequestRess().get(r);
                        //minTime = Math.min(minTime, decoupeHorizon.get(i).get(j).getProcessTime());
                        tempJob.add(decoupeHorizon.get(i).get(j));
                        if(sommeConsomRess > data.getAvailableRess().get(r)){
                            ForbidConfigs.add(tempJob);
                            toBeAdded = true;
                            //i+=Math.max(0, minTime-1);
                            break;
                        }
                    }
                }
            }
            
            //add forbiden configs to the model and to allForbidConfigs
            if(toBeAdded){   
                
                for(ArrayList<Job> forbidConf: ForbidConfigs){
                    IloLinearIntExpr expr = cplex.linearIntExpr();
                    for(Job i : forbidConf)
                    {
                        for(Job j : forbidConf)
                        {
                            expr.addTerm(1, this.x[data.getJobs().indexOf(i)][data.getJobs().indexOf(j)]);
                        }
                    }
                    cplex.addGe(expr, 1);

                    this.allForbidConfigs.add(forbidConf);
                }
            }   
        }
        
        timeToSolve = (System.nanoTime() - timeToSolve) / 1000000;
        
        //display Objective found
//        System.out.println("");
//        System.out.println("Solution with objective : " + cplex.getObjValue());
//        
//        System.out.println("Nombre de configurations interdites ajoutées (possibilité de redondance) : " + this.allForbidConfigs.size());
//        int maxP=0;
//        for (Job j: data.getJobs()) {
//            maxP = Math.max(maxP, j.getProcessTime());
//        }
//        System.out.println("");
//        System.out.println("");
        
        //Set starts of jobs for displayGanttDiagram() and displayS() methods
        startsJobs = new ArrayList<>();
        for (int i = 0; i < data.getNbJobs()+2; i++) {
            startsJobs.add((int) Math.round(cplex.getValue(S[i])));
        }
        
        this.result = new Solution(startsJobs, (int) Math.round(cplex.getObjValue()), timeToSolve);
        
        return timeToSolve;
    }
    
    /**
     * Returns solution obtained after solve() call.
     * 
     * @return solution obtained after solve() call.
     */
    @Override
    public Solution getResult(){
        return this.result;
    }
    
    /**
     * Returns the starting job cplex object corresponding to the starts job in the
     * optimal solution found.
     * 
     * @returns startsing job cplex object corresponding to the starts job in the
     * optimal solution found.
     */
    public IloNumVar[] getS() 
    {
        return S;
    }
    
}
